package particle;

//sdurant12
//11/14/2012

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
import java.util.ArrayList;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;

public class RenderClass_v5 extends JPanel implements MouseListener,
		MouseMotionListener, KeyListener {

	Random r = new Random();
	float oldX, oldY;
	ArrayList<Particle> particleAL = new ArrayList<Particle>();
	ArrayList<Graviton> gravitonAL = new ArrayList<Graviton>();
	private final int WIDTH, HEIGHT;
	private long lastTime;
	private boolean pause = false;
	private boolean emit = false;

	private BufferedImage particleImage;
	private int[] particleRaster;
	private BufferedImage blockImage;
	private int[] blockRaster;
	private int[][] densityArray;
	private Block[][] blockArray;

	public RenderClass_v5(int W, int H) {

		addMouseListener(this);
		addMouseMotionListener(this);
		addKeyListener(this);
		setFocusable(true);
		requestFocusInWindow();

		gravitonAL.add(new Graviton());

		WIDTH = W / 2;
		HEIGHT = H / 2;

		this.setBackground(Color.BLACK);

		particleImage = new BufferedImage(WIDTH, HEIGHT,
				BufferedImage.TYPE_INT_ARGB);
		particleRaster = ((DataBufferInt) particleImage.getRaster()
				.getDataBuffer()).getData();

		blockImage = new BufferedImage(WIDTH / 4, HEIGHT / 4,
				BufferedImage.TYPE_INT_ARGB);
		blockRaster = ((DataBufferInt) blockImage.getRaster().getDataBuffer())
				.getData();

		densityArray = new int[WIDTH / 32 + 1][HEIGHT / 32 + 1];

		blockArray = new Block[WIDTH / 32 + 1][HEIGHT / 32 + 1];

		for (int x_I = 0; x_I < blockArray.length; x_I++) {
			for (int y_I = 0; y_I < blockArray[1].length; y_I++) {
				Block b = new Block();
				b.setBlock(0, 0, 0, 0);

				if (y_I < 9 && y_I > 2 && x_I > 2 && x_I < 11) {
					b.type = Block.GRYSTONE;
				}
				if (y_I % 2 == 0 && x_I % 2 == 0 && x_I < WIDTH / 32 - 2
						&& x_I > 2 && y_I < HEIGHT / 32 - 2 && y_I > 2) {
					b.type = Block.GRYSTONE;
				}

				blockArray[x_I][y_I] = b;
			}
		}

	}

	private final static float InvSqrt(float x) {
		return Float
				.intBitsToFloat(0x5f3759d5 - (Float.floatToIntBits(x) >> 1));
	}

	public final static int additiveColor(int c1, int c2) {
		int red = (c1 & 0x00ff0000) + (c2 & 0x00ff0000);
		int grn = (c1 & 0x0000ff00) + (c2 & 0x0000ff00);
		int blu = (c1 & 0x000000ff) + (c2 & 0x000000ff);
		return 0xff000000 + (red > 0x00ff0000 ? 0x00ff0000 : red)
				+ (grn > 0x0000ff00 ? 0x0000ff00 : grn)
				+ (blu > 0x000000ff ? 0x000000ff : blu);
	}

	public void glow(int light, int x, int y) {

		int lightCutoff = 90;
		int lightCap = 2048;

		if (light > lightCap) {
			light = lightCap;
		}

		if (blockArray[x][y].type == 0) {

			if (blockArray[x][y].light <= light) {
				blockArray[x][y].light = light;
			}

			if (light <= lightCutoff || x <= 0 || x >= (int) (WIDTH / 32)
					|| y <= 0 || y >= (int) (HEIGHT / 32)/*
														 * if light <= 1 ||
														 * location invalid
														 */) {
				// base case
				// do nothing
			} else {

				float lightAttenuation = .8f;

				if (light > lightCutoff + blockArray[x + 1][y].light) {
					glow((int) (light * lightAttenuation), x + 1, y);
				}

				if (light > lightCutoff + blockArray[x - 1][y].light) {
					glow((int) (light * lightAttenuation), x - 1, y);
				}

				if (light > lightCutoff + blockArray[x][y + 1].light) {
					glow((int) (light * lightAttenuation), x, y + 1);
				}

				if (light > lightCutoff + blockArray[x][y - 1].light) {
					glow((int) (light * lightAttenuation), x, y - 1);
				}

			}
		} else {
			if (light <= lightCutoff || x <= 0 || x >= (int) (WIDTH / 32)
					|| y <= 0 || y >= (int) (HEIGHT / 32)) {
				// base case
				// do nothing
			} else {

				float lightAttenuation = .3f;

				if (light > lightCutoff + blockArray[x + 1][y].light) {
					glow((int) (light * lightAttenuation), x + 1, y);
				}

				if (light > lightCutoff + blockArray[x - 1][y].light) {
					glow((int) (light * lightAttenuation), x - 1, y);
				}

				if (light > lightCutoff + blockArray[x][y + 1].light) {
					glow((int) (light * lightAttenuation), x, y + 1);
				}

				if (light > lightCutoff + blockArray[x][y - 1].light) {
					glow((int) (light * lightAttenuation), x, y - 1);
				}
			}
		}

	}

	public void emitParticles(int numberSquare) {

		for (int x = 0; x <= numberSquare; x++) {
			for (int y = 0; y <= numberSquare; y++) {

				Particle p = new Particle();

				float xPos = (oldX + x - numberSquare / 2);
				float yPos = (oldY + y - numberSquare / 2);

				float Vel = r.nextFloat();

				double angle = r.nextDouble() * Math.PI * 2;

				float xVel = Vel * (float) Math.cos(angle);
				float yVel = Vel * (float) Math.sin(angle);

				p.setpxVel(xVel);
				p.setpyVel(yVel);

				p.setParticle(xPos, yPos, xVel, yVel);
//				if (blockArray[(int) ((xPos) / 32)][(int) ((yPos) / 32)].type == 0) { // if
//																						// in
//																						// air
//																						// then
//																						// add
//																						// particle
//					if (xPos <= WIDTH - 4 && xPos >= 4 && yPos <= HEIGHT - 4
//							&& yPos >= 4) {
//						densityArray[(int) ((xPos + 2) / 32)][(int) ((yPos + 2) / 32)] += 2;
//						particleAL.add(p);
//					}
//				}
				densityArray[(int) ((xPos + 2) / 32)][(int) ((yPos + 2) / 32)] += 2;
				particleAL.add(p);
			}
		}
	}

	public int makeIntColor(int A, int R, int G, int B) {
		return (A > 255 ? 0xff000000 : A << 24)
				+ (R > 255 ? 0xff0000 : R << 16) + (G > 255 ? 0xff00 : G << 8)
				+ (B > 255 ? 0xff : B);
	}

	public void paintBlock(int x, int y) {
		int light = (int) (.1 * blockArray[x][y].light);
		if (light > 200) {
			light = 200;
		}
		if (light < 0) {
			light = 0;
		}

		if (blockArray[x][y].type == 0) {
			for (int x_I = 0; x_I < 8; x_I++) {
				for (int y_I = 0; y_I < 8; y_I++) {

					blockRaster[x * 8 + x_I + (y * 8 + y_I) * WIDTH / 4] = makeIntColor(
							255, light, (int) light / 3, 0);

				}
			}
		} else if (blockArray[x][y].type == Block.GRYSTONE) {

			float leftBright = blockArray[x - 1][y].light + .5f
					* blockArray[x - 2][y].light + .25f
					* blockArray[x - 3][y].light + .125f
					* blockArray[x - 1][y - 1].light + .125f
					* blockArray[x - 1][y + 1].light;
			float rightBright = blockArray[x + 1][y].light + .5f
					* blockArray[x + 2][y].light + .25f
					* blockArray[x + 3][y].light + .125f
					* blockArray[x + 1][y - 1].light + .125f
					* blockArray[x + 1][y + 1].light;
			float upBright = blockArray[x][y - 1].light + .5f
					* blockArray[x][y - 2].light + .25f
					* blockArray[x][y - 3].light + .125f
					* blockArray[x - 1][y - 1].light + .125f
					* blockArray[x + 1][y - 1].light;
			float downBright = blockArray[x][y + 1].light + .5f
					* blockArray[x][y + 2].light + .25f
					* blockArray[x][y + 3].light + .125f
					* blockArray[x - 1][y + 1].light + .125f
					* blockArray[x - 1][y + 1].light;

			for (int x_I = 0; x_I < 8; x_I++) {
				for (int y_I = 0; y_I < 8; y_I++) {

					int normal = Block.normalIndentMap[y_I][x_I];

					int bright = 0;
					bright += leftBright * ((0xff000000 & normal) >>> 24)
							/ 0xff;
					bright += rightBright * ((0x00ff0000 & normal) >> 16)
							/ 0xff;
					bright += upBright * ((0x0000ff00 & normal) >> 8) / 0xff;
					bright += downBright * (0x000000ff & normal) / 0xff;

					blockRaster[x * 8 + x_I + (y * 8 + y_I) * WIDTH / 4] = additiveColor(
							Block.GRYSTONEINDENT_TEX[y_I][x_I],
							makeIntColor(255, bright / 12, bright / 24,
									bright / 64));
				}
			}
		}
	}

	public void tick() {

		float xPos, yPos, xVel, yVel;
		int age;
		float ClickToX, ClickToY, InvClickToP;
		int width = WIDTH;
		int height = HEIGHT;

		if (emit) {
			emitParticles(16);
		}

//		for (int x_I = 0, lightWidth = (int) (WIDTH / 32); x_I < lightWidth; x_I++) { // Draw
//																						// previous
//																						// frame's
//																						// lighting,
//																						// then
//																						// clear
//																						// lightArray
//			for (int y_I = 0, lightHeight = (int) (HEIGHT / 32); y_I < lightHeight; y_I++) {
//				paintBlock(x_I, y_I);
//			}
//		}

		if (!pause) {

//			for (int x_I = 0, lightWidth = (int) (WIDTH / 32) + 1; x_I < lightWidth; x_I++) { // clear
//																								// lightArray
//				for (int y_I = 0, lightHeight = (int) (HEIGHT / 32) + 1; y_I < lightHeight; y_I++) {
//
//					blockArray[x_I][y_I].light = (int) (.9 * blockArray[x_I][y_I].light);
//
//				}
//			}

			for (int I = 0; I < particleRaster.length; I++) { // reset
																// particleRaster
				particleRaster[I] = 0;
			}

			for (int particle_I = 0; particle_I < particleAL.size(); particle_I++) {
				Particle p = particleAL.get(particle_I);

				xPos = p.getxPos();
				yPos = p.getyPos();

				float rand = r.nextFloat() * .5f + .5f;

				xVel = p.getxVel() * (1 - rand) + p.getpxVel() * rand;
				yVel = p.getyVel() * (1 - rand) + p.getpyVel() * rand;

				age = p.getAge();

				p.setpxVel(xVel);
				p.setpyVel(yVel);
				//grivaity start
				if (gravitonAL.isEmpty()) { 
					xVel = .97f * xVel;
					yVel = .97f * yVel;
				} else {

					for (int gi = 0; gi < gravitonAL.size(); gi++) { 

						Graviton v = gravitonAL.get(gi);

						if (v != null) {

							ClickToX = v.getxPos() - xPos;
							ClickToY = v.getyPos() - yPos;
							float xPull = v.getxPull();
							float yPull = v.getyPull();

							InvClickToP = InvSqrt((ClickToX * ClickToX + ClickToY
									* ClickToY));

							xVel += xPull * ClickToX * InvClickToP;
							yVel += yPull * ClickToY * InvClickToP;

						}
					}
				}
				//gravity
				
				//out of boundary check
//				if (xPos <= width && xPos >= 0 && yPos <= height && yPos >= 0) {
//					densityArray[(int) ((xPos + 2) / 32)][(int) ((yPos + 2) / 32)] -= 2;
//				}
				//out of boundary check
				
				//in boundary chekc
//				if (xPos <= width - 64 && xPos >= 64 && yPos <= height - 64
//						&& yPos >= 64) {

						xPos += xVel;
						yPos += yVel;

//				}

				age += 100;
				//aging
				if ((Math.abs(xVel) + Math.abs(yVel)) <= .3) {
					age += 100 / (Math.abs(xVel) + Math.abs(yVel));
				}

				if (xPos <= width - 4 && xPos >= 4 && yPos <= height - 4
						&& yPos >= 4) { // in canvas

					for (int xi = -2; xi < 2; xi++) {
						for (int yi = -2; yi < 2; yi++) {
							particleRaster[(int) (xPos + xi + width
									* (int) (yPos + yi))] = additiveColor(
									particleRaster[(int) (xPos + xi + width
											* (int) (yPos + yi))], 0xff9f1604); // opaque,
																				// with
																				// blending
						}
					}

					if (densityArray[(int) ((xPos + 2) / 32)][(int) ((yPos + 2) / 32)] < 400) {
						age += 400 - densityArray[(int) ((xPos + 2) / 32)][(int) ((yPos + 2) / 32)];
					}

					if (age > 10000) {
						particleAL.remove(particle_I);
					} else {
						densityArray[(int) ((xPos + 2) / 32)][(int) ((yPos + 2) / 32)] += 2;
					}

				} else { // outside of canvas
					age += 200;
					if (age > 10000) {
						particleAL.remove(particle_I);
					}
				}

				p.setParticle(xPos, yPos, xVel, yVel);
				p.setAge(age);

			}
		}

//		for (int x_I = 0, lightWidth = (int) (WIDTH / 32) + 1; x_I < lightWidth; x_I++) {
//			for (int y_I = 0, lightHeight = (int) (HEIGHT / 32) + 1; y_I < lightHeight; y_I++) {
//
//				glow(densityArray[x_I][y_I], x_I, y_I);
//
//			}
//		}

	}

	public void paint(Graphics g) {
		super.paintComponent(g);

		//g.drawImage(blockImage, 0, 0, 2 * WIDTH, 2 * HEIGHT, null);

		g.drawImage(particleImage, 0, 0, 2 * WIDTH, 2 * HEIGHT, null);

		g.setColor(Color.WHITE);
		g.drawString("Framerate:"
				+ (1000 / (System.currentTimeMillis() - lastTime)), 5, 15);
		g.drawString("Particles : " + particleAL.size(), 5, 28);
		g.drawString("Gravity Well : " + gravitonAL.size(), 5, 41);

		for (int gi = 0, gAL = gravitonAL.size(); gi < gAL; gi++) {
			Graviton v = gravitonAL.get(gi);
			g.fillRect((int) v.getxPos() * 2, 0, 2, 16);
			g.fillRect(0, (int) v.getyPos() * 2, 16, 2);
		}

		lastTime = System.currentTimeMillis();
	}

	@Override
	public void mouseClicked(MouseEvent me) {

		if (SwingUtilities.isRightMouseButton(me)) {

			Block b = new Block();
			b.setBlock(0, 0, 0, 0);
			blockArray[(int) (oldX / 32)][(int) (oldY / 32)] = b;

		}

		if (SwingUtilities.isLeftMouseButton(me)) {

			float mouseX = me.getX() / 2;
			float mouseY = me.getY() / 2;

			Graviton v = new Graviton();

			v.setGraviton(mouseX, mouseY, .5f, .5f);
			gravitonAL.add(v);
		}

	}

	@Override
	public void mousePressed(MouseEvent me) {
	}

	@Override
	public void mouseReleased(MouseEvent me) {
		if (SwingUtilities.isRightMouseButton(me)) {

			emit = false;

		}
		if (SwingUtilities.isLeftMouseButton(me)) {

			Graviton v = new Graviton();

			gravitonAL.remove(0);

			gravitonAL.add(0, v);

		}
	}

	@Override
	public void mouseEntered(MouseEvent me) {
		requestFocusInWindow();
	}

	@Override
	public void mouseExited(MouseEvent me) {
	}

	@Override
	public void mouseDragged(MouseEvent me) {

		float mouseX = me.getX() / 2;
		float mouseY = me.getY() / 2;

		if (SwingUtilities.isRightMouseButton(me)) {

			emit = true;

		}

		if (SwingUtilities.isLeftMouseButton(me)) {

			gravitonAL.remove(0);

			Graviton v = new Graviton();

			v.setGraviton(mouseX, mouseY, 1, 1);

			gravitonAL.add(0, v);

		}

		oldX = mouseX;
		oldY = mouseY;

	}

	@Override
	public void mouseMoved(MouseEvent me) {

		oldX = me.getX() / 2;
		oldY = me.getY() / 2;

	}

	@Override
	public void keyTyped(KeyEvent ke) {

		int KeyChar = ke.getKeyChar();

		System.out.println(KeyChar);

		if (KeyChar == 27 /* ESC */) {
			pause = !pause;
		}

		if (KeyChar == 112 /* KeyEvent.VK_P */) {
			gravitonAL.clear();
			gravitonAL.add(new Graviton());
		}

		if (KeyChar == 99 /* KeyEvent.VK_C */) {
			particleAL.clear();
		}

		if (KeyChar == 120 /* KeyEvent.VK_X */) {
			for (int x_I = 0; x_I < blockArray.length; x_I++) {
				for (int y_I = 0; y_I < blockArray[1].length; y_I++) {
					blockArray[x_I][y_I].setBlock(0, 0, 0, 0);
				}
			}
		}

	}

	@Override
	public void keyPressed(KeyEvent ke) {
	}

	@Override
	public void keyReleased(KeyEvent ke) {
	}
}
